package com.panopset.io;

import static com.panopset.Util.log;
import static com.panopset.Util.logv;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

/**
 * JarResources: JarResources maps all resources included in a Zip or Jar file.
 *
 * <h5>References</h5>
 * <ul>
 * <li>
 * <a href=
 * "http://www.javaworld.com/javaworld/javatips/javatip49/JarResources.java">
 * </a> : Original source.</li>
 * <li>
 * </ul>
 *
 * @author Karl Dinwiddie
 *
 */
public final class JarResources {

    /**
     * Sizes.
     */
    private Map<String, Long> sizes;

    /**
     * Jar contents.
     */
    private Map<String, byte[]> jarContents;

    /**
     * Jar file name.
     */
    private String pjarFileName;

    /**
     * Jar file.
     */
    private File pjarFile;

    /**
     * @param jarFileName
     *            Jar or zip file path.
     */
    public JarResources(final String jarFileName) {
        pjarFileName = jarFileName;
    }

    /**
     * @param jarFile
     *            Jar or zip file.
     */
    public JarResources(final File jarFile) {
        pjarFile = jarFile;
    }

    /**
     * Get jar file.
     *
     * @return Jar file.
     */
    private File getJarFile() {
        if (pjarFile == null) {
            pjarFile = new File(pjarFileName);
        }
        return pjarFile;
    }

    /**
     * Get contents of the jar.
     *
     * @return Hashtable<String, byte[]> where the key is the resource name.
     */
    public Map<String, byte[]> getJarContents() {
        if (jarContents == null) {
            jarContents = Collections
                    .synchronizedSortedMap(new TreeMap<String, byte[]>());
            FileInputStream fis = null;
            BufferedInputStream bis = null;
            ZipInputStream zis = null;

            try {
                fis = new FileInputStream(getJarFile());
                bis = new BufferedInputStream(fis);
                zis = new ZipInputStream(bis);
                ZipEntry ze = null;
                while ((ze = zis.getNextEntry()) != null) {
                    if (ze.isDirectory()) {
                        continue;
                    }
                    logv("ze.getName()=" + ze.getName() + "," + "getSize()="
                                + ze.getSize());
                    long size = ze.getSize();
                    // -1 means unknown size.
                    if (size == -1) {
                        size = getSizes().get(ze.getName()).longValue();
                    }
                    byte[] b = new byte[(int) size];
                    int rb = 0;
                    int chunk = 0;
                    while (((int) size - rb) > 0) {
                        chunk = zis.read(b, rb, (int) size - rb);
                        if (chunk == -1) {
                            break;
                        }
                        rb += chunk;
                    }
                    // add to internal resource hashtable
                    jarContents.put(ze.getName(), b);
                    logv(ze.getName() + "  rb=" + rb + ",size=" + size
                                + ",csize=" + ze.getCompressedSize());
                }
            } catch (IOException e) {
                log(e);
            } finally {
                if (zis != null) {
                    try {
                        zis.close();
                    } catch (IOException e) {
                        log(e);
                    }
                }
            }

        }
        return jarContents;
    }

    /**
     * Get jar resource sizes.
     *
     * @return Hashtable<String, Integer> where the key is the resource name.
     * @throws IOException
     *             IO Exception.
     */
    public Map<String, Long> getSizes() throws IOException {
        if (sizes == null) {
            sizes = Collections
                    .synchronizedSortedMap(new TreeMap<String, Long>());

            ZipFile zf = new ZipFile(getJarFile());
            Enumeration<? extends ZipEntry> e = zf.entries();
            while (e.hasMoreElements()) {
                ZipEntry ze = e.nextElement();
                logv(dumpZipEntry(ze));
                sizes.put(ze.getName(), ze.getSize());
            }
            zf.close();
        }
        return sizes;
    }

    /**
     * Extracts a jar resource as a blob.
     *
     * @param name
     *            a resource name.
     * @return Resource byte array.
     * @throws IOException
     *             IO Exception.
     */
    public byte[] getResource(final String name) throws IOException {
        return getJarContents().get(name);
    }

    /**
     * Dumps a zip entry into a string.
     *
     * @param ze
     *            a ZipEntry
     * @return Dumped zip entry.
     */
    private String dumpZipEntry(final ZipEntry ze) {
        StringBuilder sb = new StringBuilder();
        if (ze.isDirectory()) {
            sb.append("d ");
        } else {
            sb.append("f ");
        }
        if (ze.getMethod() == ZipEntry.STORED) {
            sb.append("stored   ");
        } else {
            sb.append("defalted ");
        }
        sb.append(ze.getName());
        sb.append("\t");
        sb.append("" + ze.getSize());
        if (ze.getMethod() == ZipEntry.DEFLATED) {
            sb.append("/" + ze.getCompressedSize());
        }
        return (sb.toString());
    }

    /**
     * Test driver. Given a jar file and a resource name, it trys to extract the
     * resource and then tells us whether it could or not.
     *
     * <strong>Example</strong> Let's say you have a JAR file which jarred up a
     * bunch of gif image files. Now, by using JarResources, you could extract,
     * create, and display those images on-the-fly.
     *
     * <pre>
     *     ...
     *     JarResources JR=new JarResources("GifBundle.jar");
     *     Image image=Toolkit.createImage(JR.getResource("logo.gif");
     *     Image logo=Toolkit.getDefaultToolkit().createImage(
     *                   JR.getResources("logo.gif")
     *                   );
     *     ...
     * </pre>
     *
     * @param args 0 = jar file name, 1 = resource name.
     * @throws IOException IO Exception.
     */
    public static void main(final String[] args) throws IOException {
        if (args.length != 2) {
            System.err.println("usage: "
                    + "java JarResources <jar file name> <resource name>");
            System.exit(1);
        }
        JarResources jr = new JarResources(args[0]);
        byte[] buff = jr.getResource(args[1]);
        if (buff == null) {
            System.out.println("Could not find " + args[1] + ".");
        } else {
            System.out.println("Found " + args[1] + " (length=" + buff.length
                    + ").");
        }
    }

}
